package org.orbroker

import java.sql.SQLException
import org.orbroker.exception._
import scala.collection.mutable.Buffer
import scala.collection.mutable.ArrayBuffer

private[orbroker] trait Queryable extends Session {

  protected def queryFromSelect[T](token: Token[T], qs: QueryStatement, parms: Seq[(String, Any)], receiver: T ⇒ Boolean) {
    val startTime = System.nanoTime
    try {
      val (sql, values) = qs.query(token, this, toMap(parms), receiver)
      callback.onSQL(token.id, sql, values, diffTimeInMicros(startTime))
    } catch {
      case e: SQLException ⇒ throw evaluate(e)
    }
  }

  protected def queryFromCall[T](token: Token[T], cs: CallStatement, parms: Seq[(String, Any)], receiver: T ⇒ Boolean)

  /**
   * Execute query and call receiver function per result object.
   * @param id ID of SQL statement
   * @param parms Parameters for statement, if any
   * @param receiver The result object receiver function. 
   * Should return `true` to receive next object, `false` to stop
   */
  def select[T](token: Token[T], parms: (String, Any)*)(receiver: T ⇒ Boolean) {
    getStatement(token) match {
      case qs: QueryStatement ⇒ queryFromSelect(token, qs, parms, receiver)
      case cs: CallStatement ⇒ queryFromCall(token, cs, parms, receiver)
      case _ ⇒ throw new ConfigurationException("Statement '%s' is not a query".format(token.id.name))
    }
  }

  /**
   * Execute query that will return 0-1 rows 
   * (by id, a JOIN of course can return more rows, but only one object).
   * @param id The ID of the statement to execute
   * @param parms Optional SQL parameters
   * @return The selected row, or `None`
   */
  @throws(classOf[MoreThanOneException])
  def selectOne[T](token: Token[T], parms: (String, Any)*): Option[T] = {
    var maybe: Option[T] = None
    select(token, parms: _*) { result: T ⇒
      if (maybe == None) {
        maybe = Some(result)
      } else {
        throw new MoreThanOneException(token.id, "returned")
      }
      true
    }
    maybe
  }

  /**
   * Execute query and return sequence of all results.
   * @param token The token of the statement to execute
   * @param parms Optional SQL parameters
   * @return The sequence of results.
   */
  def selectAll[T](token: Token[T], parms: (String, Any)*): IndexedSeq[T] = {
    val buffer = new ArrayBuffer[T](64)
    select(token, parms: _*) { t =>
      buffer += t
      true
    }
    buffer
  }

  /**
   * Execute query and return the top results
   * as defined by the `count` parameter.
   * @param count The top number of results to return
   * @param token The token of the statement to execute
   * @param parms Optional SQL parameters
   * @return The sequence of results.
   */
  def selectTop[T](count: Int, token: Token[T], parms: (String, Any)*): IndexedSeq[T] = {
    if (count <= 0) {
      IndexedSeq.empty
    } else {
      val userFetchSize = fetchSize
      if (userFetchSize == 0 || userFetchSize > count) {
        fetchSize = count
      }
      val buffer = new ArrayBuffer[T](count)
      select(token, parms: _*) { t ⇒
        buffer += t
        buffer.size < count
      }
      fetchSize = userFetchSize
      buffer
    }
  }

}
